package com.xish.api.activity.search.query;

import com.mysema.query.types.Expression;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.expr.BooleanExpression;
import com.mysema.query.types.expr.NumberExpression;
import com.mysema.query.types.path.NumberPath;
import com.xish.api.GeoLocation;
import com.xish.api.SqlQueryHolder;
import com.xish.api.activity.search.ActivitySearchCondition;
import com.xish.util.DateTimeUtil;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

import static com.mysema.query.support.Expressions.constant;
import static com.mysema.query.types.expr.MathExpressions.*;
import static com.xish.model.table.QWzmActivitySearch.wzmActivitySearch;

public abstract class ActivitySearchSqlQuery implements SqlQueryHolder {

    private static final Logger LOG = LoggerFactory.getLogger(ActivitySearchSqlQuery.class);

    public static final Double showCloseDistance = 30D;
    public static final Double showFarDistance = 2D;

    protected ActivitySearchCondition condition;
    protected List<Integer> excludeIdList;

    //格式和数据库不一致
    protected NumberPath<Double> lat = new NumberPath<>(Double.class, wzmActivitySearch, "lat");
    protected NumberPath<Double> lon = new NumberPath<>(Double.class, wzmActivitySearch, "lon");
    protected NumberPath<Integer> type = new NumberPath<>(Integer.class, wzmActivitySearch, "schedule_type");

    protected ActivitySearchSqlQuery(ActivitySearchCondition condition, List<Integer> excludeIdList) {
        this.condition = condition;
        this.excludeIdList = excludeIdList;
    }

    public BooleanExpression idNotIn(List<Integer> list) {
        if ((list != null) && !list.isEmpty()) {
            return wzmActivitySearch.id.notIn(list);
        } else {
            return null;
        }
    }

    public BooleanExpression status() {
        return wzmActivitySearch.status.eq(false);
    }

    public BooleanExpression isShowWeek(boolean value) {
        return wzmActivitySearch.isshowWeek.eq(value);
    }

    public BooleanExpression isShowWeek() {
        return wzmActivitySearch.isshowWeek.eq(true);
    }

    public BooleanExpression createTime() {
        long time = new DateTime().minusHours(36).toDate().getTime() / 1000L;
        return wzmActivitySearch.createTime.goe(time);
    }

    public BooleanExpression showEndTime() {
        long time = System.currentTimeMillis() / 1000L;
        return wzmActivitySearch.showEndtime.gt(time);
    }

    public BooleanExpression startTimeBegin() {
        long time = DateTimeUtil.thisWeekSaturday().toDate().getTime() / 1000L;
        return wzmActivitySearch.startTime.goe(time);
    }

    public BooleanExpression startTimeEnd() {
        long time = DateTimeUtil.nextWeekMonday().toDate().getTime() / 1000L;
        return wzmActivitySearch.startTime.loe(time);
    }

    public BooleanExpression endTimeBegin() {
        long time = DateTimeUtil.thisWeekSaturday().toDate().getTime() / 1000L;
        long currentTime = System.currentTimeMillis() / 1000L;
        return wzmActivitySearch.endTime.goe(Math.max(time, currentTime));
    }

    public BooleanExpression endTimeEnd() {
        long time = DateTimeUtil.nextWeekMonday().toDate().getTime() / 1000L;
        return wzmActivitySearch.endTime.loe(time);
    }

    public NumberExpression<Double> distance(GeoLocation location) {
        Expression<Double> myLat = constant(location.getLatitude());
        Expression<Double> myLon = constant(location.getLongitude());

        //sin( radians($lat) ) * sin( radians(lat)
        NumberExpression<Double> e1 = sin(radians(myLat)).multiply(sin(radians(lat)));
        //cos( radians(lon) - radians($lon) )
        NumberExpression<Double> e2 = cos(radians(lon).subtract(radians(myLon)));
        //cos( radians($lat) ) * cos( radians(lat) ) * e2
        NumberExpression<Double> e3 = cos(radians(myLat)).multiply(cos(radians(lat))).multiply(e2);
        //e3 + e1
        NumberExpression<Double> e4 = e3.add(e1);

        //6371 * acos(e4)
        return acos(e4).multiply(constant(6371D));
    }

    public BooleanExpression showClose() {
        BooleanExpression distanceExpression;
        if (condition.hasGeoLocation()) {
            distanceExpression = distance(condition.getGeoLocation()).gt(showCloseDistance);
        } else {
            distanceExpression = wzmActivitySearch.id.eq(0);
        }

        BooleanExpression showCloseExpression = wzmActivitySearch.isshowClose.eq(true).and(distanceExpression);
        //特别说明框架对OR处理的原因导致必须写2次
        BooleanExpression notShowCloseExpression = wzmActivitySearch.isshowClose.eq(false).or(wzmActivitySearch.isshowClose.eq(false));

        LOG.debug("distanceExpression: {}", distanceExpression);
        LOG.debug("showCloseExpression: {}", showCloseExpression);
        LOG.debug("notShowCloseExpression: {}", notShowCloseExpression);

        return showCloseExpression.or(notShowCloseExpression);
    }

    public BooleanExpression showFar() {
        BooleanExpression distanceExpression;
        if (condition.hasGeoLocation()) {
            distanceExpression = distance(condition.getGeoLocation()).lt(showFarDistance);
        } else {
            distanceExpression = wzmActivitySearch.id.eq(0);
        }

        BooleanExpression showFarExpression = wzmActivitySearch.isshowFar.eq(true).and(distanceExpression);
        //特别说明框架对OR处理的原因导致必须写2次
        BooleanExpression notShowFarExpression = wzmActivitySearch.isshowFar.eq(false).or(wzmActivitySearch.isshowFar.eq(false));

        LOG.debug("distanceExpression: {}", distanceExpression);
        LOG.debug("showFarExpression: {}", showFarExpression);
        LOG.debug("notShowFarExpression: {}", notShowFarExpression);

        return showFarExpression.or(notShowFarExpression);
    }

    public BooleanExpression scheduleType() {
        return type.eq(4);
    }

    public BooleanExpression cityPoiId() {
        if (condition.getCityPoiId() > 0) {
            return wzmActivitySearch.cityPoiId.eq(condition.getCityPoiId());
        }

        return null;
    }

    public OrderSpecifier sortDayFollowNum() {
        return wzmActivitySearch.dayFollowNum.desc();
    }

    public OrderSpecifier sortDistance() {
        return distance(condition.getGeoLocation()).asc();
    }

    public OrderSpecifier sortDateNew() {
        long time = System.currentTimeMillis() / 1000L;
        Expression<Long> currentTime = constant(time);
        return wzmActivitySearch.startTime.subtract(currentTime).abs().asc();
    }

    public OrderSpecifier sortPriceAsc() {
        return wzmActivitySearch.lowestPrice.asc();
    }

    public OrderSpecifier sortPriceDesc() {
        return wzmActivitySearch.lowestPrice.desc();
    }

    public OrderSpecifier sortNewest() {
        return wzmActivitySearch.onlineTime.desc();
    }

    public OrderSpecifier sortStartTime() {
        return wzmActivitySearch.startTime.desc();
    }
}
